#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <test/jtx/envconfig.h>

#include <xrpl/protocol/jss.h>

#include <boost/container/static_vector.hpp>

#include <algorithm>

namespace ripple {

class TransactionHistory_test : public beast::unit_test::suite
{
    void
    testBadInput()
    {
        testcase("Invalid request params");
        using namespace test::jtx;
        Env env{*this, envconfig(no_admin)};

        {
            // no params
            auto const result =
                env.client().invoke("tx_history", {})[jss::result];
            BEAST_EXPECT(result[jss::error] == "invalidParams");
            BEAST_EXPECT(result[jss::status] == "error");
        }

        {
            // test at 1 greater than the allowed non-admin limit
            Json::Value params{Json::objectValue};
            params[jss::start] = 10001;  // limited to <= 10000 for non admin
            auto const result =
                env.client().invoke("tx_history", params)[jss::result];
            BEAST_EXPECT(result[jss::error] == "noPermission");
            BEAST_EXPECT(result[jss::status] == "error");
        }
    }

    void
    testCommandRetired()
    {
        testcase("Command retired from API v2");
        using namespace test::jtx;
        Env env{*this, envconfig(no_admin)};

        Json::Value params{Json::objectValue};
        params[jss::api_version] = 2;
        auto const result =
            env.client().invoke("tx_history", params)[jss::result];
        BEAST_EXPECT(result[jss::error] == "unknownCmd");
        BEAST_EXPECT(result[jss::status] == "error");
    }

    void
    testRequest()
    {
        testcase("Basic request");
        using namespace test::jtx;
        Env env{*this};

        // create enough transactions to provide some
        // history...
        size_t const numAccounts = 20;
        boost::container::static_vector<Account, numAccounts> accounts;
        for (size_t i = 0; i < numAccounts; ++i)
        {
            accounts.emplace_back("A" + std::to_string(i));
            auto const& acct = accounts.back();
            env.fund(XRP(10000), acct);
            env.close();
            if (i > 0)
            {
                auto const& prev = accounts[i - 1];
                env.trust(acct["USD"](1000), prev);
                env(pay(acct, prev, acct["USD"](5)));
            }
            env(offer(acct, XRP(100), acct["USD"](1)));
            env.close();

            // verify the latest transaction in env (offer)
            // is available in tx_history.
            Json::Value params{Json::objectValue};
            params[jss::start] = 0;
            auto result =
                env.client().invoke("tx_history", params)[jss::result];
            if (!BEAST_EXPECT(
                    result[jss::txs].isArray() && result[jss::txs].size() > 0))
                return;

            // search for a tx in history matching the last offer
            bool const txFound = [&] {
                auto const toFind = env.tx()->getJson(JsonOptions::none);
                for (auto tx : result[jss::txs])
                {
                    tx.removeMember(jss::inLedger);
                    tx.removeMember(jss::ledger_index);
                    if (toFind == tx)
                        return true;
                }
                return false;
            }();
            BEAST_EXPECT(txFound);
        }

        unsigned int start = 0;
        unsigned int total = 0;
        // also summarize the transaction types in this map
        std::unordered_map<std::string, unsigned> typeCounts;
        while (start < 120)
        {
            Json::Value params{Json::objectValue};
            params[jss::start] = start;
            auto result =
                env.client().invoke("tx_history", params)[jss::result];
            if (!BEAST_EXPECT(
                    result[jss::txs].isArray() && result[jss::txs].size() > 0))
                break;
            total += result[jss::txs].size();
            start += 20;
            for (auto const& t : result[jss::txs])
            {
                typeCounts[t[sfTransactionType.fieldName].asString()]++;
            }
        }
        BEAST_EXPECT(total == 117);
        BEAST_EXPECT(typeCounts[jss::AccountSet.c_str()] == 20);
        BEAST_EXPECT(typeCounts[jss::TrustSet.c_str()] == 19);
        BEAST_EXPECT(typeCounts[jss::Payment.c_str()] == 58);
        BEAST_EXPECT(typeCounts[jss::OfferCreate.c_str()] == 20);

        // also, try a request with max non-admin start value
        {
            Json::Value params{Json::objectValue};
            params[jss::start] = 10000;  // limited to <= 10000 for non admin
            auto const result =
                env.client().invoke("tx_history", params)[jss::result];
            BEAST_EXPECT(result[jss::status] == "success");
            BEAST_EXPECT(result[jss::index] == 10000);
        }
    }

public:
    void
    run() override
    {
        testBadInput();
        testRequest();
        testCommandRetired();
    }
};

BEAST_DEFINE_TESTSUITE(TransactionHistory, rpc, ripple);

}  // namespace ripple
